﻿using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Security;
using System.Threading;
using SeleniumToolkit.Config;

namespace SeleniumToolkit.Core
{
    /// <summary>
    /// Controls Selenium RC process life cycle
    /// </summary>
    public class SeleniumRemoteControlHost : ISeleniumProcess
    {
        #region Constructor
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="config">Selenium runtime config</param>
        public SeleniumRemoteControlHost(SeleniumConfiguration config)
        {
            _config = config;
        } 
        #endregion

        #region Properties
        /// <summary>
        /// Folder location of the selenium-server.jar
        /// </summary>
        protected string Folder
        {
            get { return _config.RuntimeSettings.SeleniumRuntimePath; }
        }

        /// <summary>
        /// Name of the selenium-rc server, ie localhost
        /// </summary>
        protected string ServerName
        {
            get { return _config.SeleniumServer; }
        }

        /// <summary>
        /// Port of the selenium-rc instance, ie 4444
        /// </summary>
        protected int Port
        {
            get { return _config.SeleniumServerPort; }
        }

        /// <summary>
        /// Flag to determine if the active process window should be hidden when running
        /// </summary>
        protected bool HideProcess
        {
            get { return _config.RuntimeSettings.HideRuntimeProcess; }
        }

        /// <summary>
        /// Username for the host process
        /// </summary>
        protected string UserName
        {
            get { return _config.RuntimeSettings.UserName; }
        }

        /// <summary>
        /// Password for the host process
        /// </summary>
        protected string Password
        {
            get { return _config.RuntimeSettings.Password; }
        } 
        #endregion

        #region ISeleniumProcess implementation
        /// <summary>
        /// Starts the selenium-rc process
        /// </summary>
        public void Start()
        {
            EnsureJavaRuntimeAvailable();

            if (!Running)
            {
                Process remoteControl = Process.Start(GetStartInfo());
                int timeout = 0;
                while (!Running && timeout < 5000)
                {
                    Thread.Sleep(200);
                    timeout += 200;
                }
                if (!remoteControl.HasExited)
                {
                    _processId = remoteControl.Id;
                }
            }
        }

        /// <summary>
        /// Stops the selenium-rc process
        /// </summary>
        public void Stop()
        {
            if (_processId != 0)
            {
                var process = Process.GetProcessById(_processId);
                process.Kill();
                process.WaitForExit();
                _config.UserExtensions.CleanupTempFile();
            }
        }

        /// <summary>
        /// Determines if the selenium-rc process is running
        /// </summary>
        public bool Running
        {
            get
            {
                bool result = true;
                using (var portScanner = new TcpClient())
                {
                    try
                    {
                        //var portScanner = new TcpClient();
                        portScanner.Connect(ServerName, Port);
                        portScanner.Close();
                    }
                    catch
                    {
                        result = false;
                    }
                }
                return result;
            }
        } 
        #endregion

        #region Private Members
        /// <summary>
        /// Determines if java is installed by checking for a CLASSPATH /PATH environment variable
        /// </summary>
        private static void EnsureJavaRuntimeAvailable()
        {
            string classPath = Environment.GetEnvironmentVariable("CLASSPATH");
            string path = Environment.GetEnvironmentVariable("PATH");

            bool classPathFound = !String.IsNullOrEmpty(classPath);
            bool pathHasJava = (!String.IsNullOrEmpty(path) && path.IndexOf("java", StringComparison.OrdinalIgnoreCase) > 0);

            if (pathHasJava)
                return;

            if (classPathFound)
                return;

            throw new NotSupportedException("Unable to start selenium host process because Java was not found.  Please add Java's folder location to the PATH Environment variable.  You may need to logoff to apply your changes, btw.");
        }


        private ProcessStartInfo GetStartInfo()
        {
            var info = new ProcessStartInfo("java.exe", GetArguments());
            //info.UseShellExecute = false;
            if (HideProcess)
            {
                info.WindowStyle = ProcessWindowStyle.Hidden;
            }
            if (!String.IsNullOrEmpty(UserName))
            {
                info.UserName = UserName;
                if (!String.IsNullOrEmpty(Password))
                {
                    var secure = new SecureString();
                    foreach (char letter in Password)
                    {
                        secure.AppendChar(letter);
                    }
                    info.Password = secure;
                }
            }
            return info;
        }

        /// <summary>
        /// Gets the runtime arguments for the java process
        /// </summary>
        /// <returns></returns>
        private string GetArguments()
        {
            var argumentBuilder = new SeleniumRemoteControlArgumentBuilder(_config);
            return argumentBuilder.GetArguments();
        } 
        #endregion

        private readonly SeleniumConfiguration _config;
        private int _processId;
    }
}
